<?php
/**
 *自动备货(需求c)商品模型
 */
class Cron_Model_Backup_AutoBackupProduct extends Model_Base {
	private $_goodsSn;
	//出需求的仓库
	private $_warehouseCode;
	//商品使用的备货规则
	private $_productBackupRule;
	//商品采购数据
	private $_productPurchaseInfo;
	//产品已有的待审核需求
	private $_productStory;
	//产品库存信息
	private $_productStockInfo;

	//产品平均销量
	private $_mean_sales;
	//产品备货天数
	private $_backupDays;
	//商品自动备货需求备货数
	private $_backupQuantity;

	//产品安全库存
	private $_safeStockQuantity;

	//备货计算需要依赖的其他模型
	private $_productSalesModel;
	private $_productPurchaseInfoModel;
	private $_productSalesMeanStrategy;
	private $_storyModel;
	private $_storyBackupLogModel;

	public function __construct($goodsSn, $warehouseCode, array $rule) {
		$this->db = $this->db();
		$this->_goodsSn = $goodsSn;
		$this->_warehouseCode = $warehouseCode;
		$this->_productBackupRule = $rule;
		$this->_initDependencyModel();
	}

	/**
	 * 初始化模型依赖
	 * @return [type] [description]
	 */
	private function _initDependencyModel() {
		$this->_productSalesModel = new Application_Model_ProductSales();
		$this->_productPurchaseInfoModel = new Application_Model_ProductPurchaseInfo();
		$this->_storyModel = new Cron_Model_Backup_Story();
		$this->_storyBackupLogModel = new Cron_Model_Backup_Log($this->_goodsSn, $this->_warehouseCode);
		$this->_warehouseProductModel = new Application_Model_WarehouseProduct();
	}

	/**
	 * 生成自动备货需求(c需求)
	 * @return [type] [description]
	 */
	public function createStory() {
		//检查商品是否符合c需求计算要求
		try {
			$this->_storyBackupLogModel->log('c_story_use_rule_id', $this->_productBackupRule['id']);
			$conformResult = $this->_isConformAutoBackupStandard();
			//不符合c需求计算要求，直接返回
			if ($conformResult === false) {
				$this->_storyBackupLogModel->write();
				return true;
			}
		} catch (Exception $e) {
			return $this->_error($e->getMessage());
		}

		$backupQuantity = $this->_needBackupQuantity();
		if ($backupQuantity === false) {
			$this->_storyBackupLogModel->write();
			return false;
		}

		//记录备货计算日志数据
		if (bccomp($backupQuantity, 0) === 0) {
			$this->_storyBackupLogModel->write();
			return true;
		}
		try {
			//检查需求c是否要被拦截
			if (!$this->_storyInterceptCheck()) {
				$createCStoryRe = $this->_storyModel->createInterceptStory($this->_goodsSn, $this->_warehouseCode, Application_Model_Story::C_STORY_TYPE, $backupQuantity, 1);
				$this->_storyBackupLogModel->log('c_story_is_intercept', 1);
			} else {
				$createCStoryRe = $this->_storyModel->create($this->_goodsSn, $this->_warehouseCode, Application_Model_Story::C_STORY_TYPE, $backupQuantity);
			}
		} catch (Exception $e) {
			$this->_storyBackupLogModel->write();
			return $this->_error($e->getMessage());
		}

		if (!$createCStoryRe) {
			$this->_storyBackupLogModel->write();
			return $this->_error($this->_storyModel->getErrorMsg());
		}
		$this->_storyBackupLogModel->write();
		return true;
	}

	/**
	 * 计算商品需要的备货数
	 * @param  [type] $goodsSn       需要备货的商品sku
	 * @param  [type] $warehouseCode 备货商品所属的仓库代码
	 * @param  [type] $warehouseCode 计算备货数使用的备货规则id
	 * @return [type]                [description]
	 */
	private function _needBackupQuantity() {
		$this->_backupQuantity = $this->_calculateBackupQuantity();
		if ($this->_backupQuantity === false) {
			return false;
		}
		$this->_storyBackupLogModel->log("c_story_backup_init_quantity", $this->_backupQuantity);

		if ($this->_backupQuantityModify() === false) {
			return false;
		}
		return $this->_backupQuantity;
	}

	/**
	 * 获取商品的平均销量
	 * @return [type] [description]
	 */
	private function _getSalesMean() {
		if ($this->_mean_sales) {
			return $this->_mean_sales;
		}
		$productSalesQuantityMean = $this->_calculateSalesMean();
		if ($productSalesQuantityMean === false) {
			return false;
		}
		$this->_mean_sales = round($productSalesQuantityMean, 2);
		$this->_storyBackupLogModel->log("mean_sales", $this->_mean_sales);
		return $this->_mean_sales;
	}

	/**
	 *根据(平均销量*备货天数)计算商品备货量
	 * @return [type] [description]
	 */
	private function _calculateBackupQuantity() {
		$meanSales = $this->_getSalesMean();
		if ($meanSales === false) {
			return false;
		}
		$stockDays = $this->_getBackupDays();
		if ($stockDays === false) {
			return false;
		}
		$backupQuantity = $meanSales * $stockDays;
		return round($backupQuantity, 2);
	}

	/**
	 * 检查商品是否符合满足需要 “自动备货” 的规则
	 * @return boolean [description]
	 */
	private function _isConformAutoBackupStandard() {
		$needCheckItem = [
			'safeStock',
			'clearance',
		];

		$isConform = true;
		foreach ($needCheckItem as $item) {
			$checkMethod = "_" . $item . "Check";
			try {
				$checkResult = $this->$checkMethod();
				if ($checkResult === false) {
					return false;
				}
			} catch (Exception $e) {
				throw $e;
			}

		}
		return $isConform;
	}

	/**
	 *安全库存检查；（库存数量-90天内正常未发货订单占用来量）低于备货规则中设定的安全库存则不备货
	 */
	private function _safeStockCheck() {
		$safeStockQuantity = $this->_calculateSafeStock();
		if ($safeStockQuantity === false) {
			throw new Exception($this->getErrorMsg());
		}
		$this->_safeStockQuantity = round($safeStockQuantity, 2);
		$this->_storyBackupLogModel->log("safe_stock", $this->_safeStockQuantity);

		$productStock = $this->_getStock();
		if ($productStock === false) {
			throw new Exception($this->getErrorMsg());
		}
		$this->_storyBackupLogModel->log('stock', $productStock);

		$the90DaysOrder = $this->_90DaysOrderAmount();
		if ($the90DaysOrder === false) {
			throw new Exception($this->getErrorMsg());
		}
		$this->_storyBackupLogModel->log('the_90_days_order', $the90DaysOrder);
		if (($productStock - $the90DaysOrder) >= $this->_safeStockQuantity) {
			$this->_storyBackupLogModel->log("c_story_fail_remark", '库存大于安全库存');
			return false;
		}
		return true;
	}

	/**
	 * 产品是否清仓检查
	 * @return boolean [description]
	 */
	private function _clearanceCheck() {
		if ($this->_warehouseProductModel->isClearance($this->_goodsSn, $this->_warehouseCode)) {
			$this->_storyBackupLogModel->log("c_story_fail_remark", '产品清仓');
			$this->_storyBackupLogModel->log("is_clearance", 1);
			return false;
		}
		$this->_storyBackupLogModel->log("is_clearance", 0);
		return true;
	}

	/**
	 * 获取备货规则设置的商品的安全库存
	 * @return [type] [description]
	 */
	private function _calculateSafeStock() {
		$safeStockCalculateType = $this->_productBackupRule['safe_stock_type'];
		if ($safeStockCalculateType == 1) {
			return $this->_calculateSafeStockBySales();
		} elseif ($safeStockCalculateType == 2) {
			return $this->_calculateSafeStockByDay();
		}
	}

	/**
	 * 根据销量计算安全库存
	 * @return [type] [description]
	 */
	private function _calculateSafeStockBySales() {
		$safeStockBase = $this->_productBackupRule['safe_stock_base'];

		$safeStockBaseNumber = $this->_calculateBase($safeStockBase);
		if ($safeStockBaseNumber === false) {
			$this->_error("安全库存的基数计算发生错误，错误原因:" . $this->getErrorMsg());
		}

		$safeStcokFactor = (float) $this->_productBackupRule['safe_stock_factor'];

		if (!$safeStcokFactor) {
			return $this->_error("安全库存量因子" . $safeStcokFactor . "参数类型错误！");
		}

		return $safeStockBaseNumber * $safeStcokFactor;
	}

	/**
	 * 根据天数计算安全库存
	 * @return [type] [description]
	 */
	private function _calculateSafeStockByDay() {
		$safeStockDeliveryDaysType = $this->_productBackupRule['safe_stock_delivery_days_type'];
		$productDeliveryDays = $this->_calculateDeliveryDays($safeStockDeliveryDaysType);
		if ($productDeliveryDays === false) {
			return $this->_error("安全库存按天计算方案的交期天数计算发生错误,错误原因:" . $this->getErrorMsg());
		}

		$safeStockDays = $productDeliveryDays + $this->_productBackupRule['safe_stock_shelves_days'] + $this->_productBackupRule['safe_stock_quality_testing_days'] + $this->_productBackupRule['safe_stock_need_backup_days'];
		$meanSales = $this->_getSalesMean();
		if ($meanSales === false) {
			return false;
		}
		return $safeStockDays * $meanSales;
	}

	/**
	 * 需求数量更新(根据 平均销量*备货天数 计算出来的备货数量会因为 产品需求A的数量，最小备货量限制 等而被更新)
	 * @return [type] [description]
	 */
	private function _backupQuantityModify() {
		//需求c数量需要被检测项
		$modifyTestItem = [
			'stockoutStoryQuantityTest',
			'minBackupQuantityTest',
			'maxBackupQuantityTest',
		];
		//需求c数量需要被检测项名称
		$modifyTestItemName = [
			'stockoutStoryQuantityTest' => '需求A',
			'minBackupQuantityTest' => '最小备货量限制',
			'maxBackupQuantityTest' => '最大备货量限制',
		];
		//需求c数量经过被检测项后的数量的日志记录名称(被用于日后显示需求c经过这些检测项后，数量是如何变化的)
		$afterModifyTestQuantityLogItemName = [
			'stockoutStoryQuantityTest' => 'after_a_story_limit_c_story_quantity',
			'minBackupQuantityTest' => 'after_min_limit_c_story_quantity',
			'maxBackupQuantityTest' => 'after_max_limit_c_story_quantity',
		];

		foreach ($modifyTestItem as $item) {
			$cStoryStartQuantity = $this->_backupQuantity;

			$method = "_" . $item;
			$backupQuantityTestResult = $this->$method();
			if ($backupQuantityTestResult === false) {
				return false;
			}
			$cStoryEndQuantity = $this->_backupQuantity;
			$cStoryQuantityChange = bcsub($cStoryEndQuantity, $cStoryStartQuantity, 2);

			//记录需求c经过各检测项后的数量变化(因为取整造成的变化，不记录)
			if ($cStoryQuantityChange > 1) {
				//因为需求A导致的变化，暂不记录日志
				if ($method != "_stockoutStoryQuantityTest") {
					$this->_storyBackupLogModel->log('c_story_quantity_modify_remark', '因' . $modifyTestItemName[$item] . '，' . ($cStoryQuantityChange > 0 ? '增加' : '减少') . '需求C的数量:' . abs($cStoryQuantityChange . ";\r\n"));
				}

			}

			//记录需求c经过检测项后的值
			$this->_storyBackupLogModel->log($afterModifyTestQuantityLogItemName[$item], $cStoryEndQuantity);
			//如果需求c值经过检测项后的值变为了零，终止进行下一步检测
			if ($this->_backupQuantity === 0) {
				$this->_storyBackupLogModel->log('c_story_quantity_modify_remark', '因' . $modifyTestItemName[$item] . '，需求C的数量变为了零！');
				return true;
			}
		}
		return true;
	}

	/**
	 * 备货数量经过“是否已经产生了缺货需求（A需求）” 检测
	 * @return [type]                          [description]
	 */
	private function _stockoutStoryQuantityTest() {
		$stockoutStoryBackupQuantity = $this->_getProductStoryHasQuantity(Application_Model_Story::A_STORY_TYPE);
		if ($stockoutStoryBackupQuantity === false) {
			return false;
		}

		//当需求A等于0时，未缺货，则自动备货量=备货数量估算值-(库存-90天订单占用)
		if ($stockoutStoryBackupQuantity == 0) {
			$productStock = $this->_getStock();
			if ($productStock === false) {
				return false;
			}
			$the90DaysOrder = $this->_90DaysOrderAmount();
			if ($the90DaysOrder === false) {
				return false;
			}
			$this->_backupQuantity = $this->_backupQuantity - ($productStock - $the90DaysOrder);
		}
		$this->_backupQuantity = (int) round($this->_backupQuantity);
		return true;
	}

	/**
	 * 获取商品库存（库存=订货数量+可配库存+待发库存+在途库存）
	 * @return [type] [description]
	 */
	private function _getStock() {
		$stockInfo = $this->_getStockInfo();
		if (is_array($stockInfo) && empty($stockInfo)) {
			return 0;
		}
		return $stockInfo['stock_order_nums'] + $stockInfo['stock_can_allot'] + $stockInfo['stock_wait_for_send'] + $stockInfo['stock_on_way'];
	}

	/**
	 * 获取商品90天内的订单占用量
	 * @return [type] [description]
	 */
	private function _90DaysOrderAmount() {
		$stockInfo = $this->_getStockInfo();
		return $stockInfo['order_occupy_quarter'] ?? 0;
	}

	/**
	 * 获取商品的库存信息
	 * @return [type] [description]
	 */
	private function _getStockInfo() {
		if (is_array($this->_productStockInfo)) {
			return $this->_productStockInfo;
		}
		$where = $this->db->quoteInto("goods_sn=?", $this->_goodsSn);
		$where .= " AND " . $this->db->quoteInto("warehouse_code=?", $this->_warehouseCode);
		$stockInfo = $this->db->fetchRow('select * from p_product_warehouse_stock where ' . $where);
		//有些商品会没有库存信息
		if (!$stockInfo) {
			$stockInfo = [];
		}
		$this->_productStockInfo = $stockInfo;
		return $this->_productStockInfo;
	}

	/**
	 * 获取备货数量经过 “最小备货量” 检测
	 * @return [type]                [description]
	 */
	private function _minBackupQuantityTest() {
		$minStock = $this->_productBackupRule['min_backup'];
		$this->_storyBackupLogModel->log("c_story_min_quantity", $minStock);

		if ($minStock >= 0) {
			if ($this->_backupQuantity < $minStock) {
				$this->_backupQuantity = 0;
			}
			return true;
		}
		return $this->_error('备货规则设置的最小备货量:' . $minStock . '不合法！');
	}

	/**
	 * 获取备货数量经过 “最大备货量” 检测
	 * @return [type]                          [description]
	 */
	private function _maxBackupQuantityTest() {
		$maxStockBaseCode = $this->_productBackupRule['max_backup_base'];
		$maxStockBaseNumber = $this->_calculateBase($maxStockBaseCode);
		if ($maxStockBaseNumber === false) {
			$this->_error("备货规则的最大备货量基数计算发生错误，错误原因:" . $this->getErrorMsg());
			return false;
		}

		$maxStockFactor = $this->_productBackupRule['max_backup_factor'];
		$maxStockFactor = (float) $maxStockFactor;
		if (!is_float($maxStockFactor)) {
			return $this->_error("备货规则的最大备货量的因子参数" . $maxStockFactor . "类型错误！");
		}

		$maxBackupQuantity = $maxStockBaseNumber * $maxStockFactor;
		$this->_storyBackupLogModel->log("c_story_max_quantity", $maxBackupQuantity);

		if ($this->_backupQuantity > $maxBackupQuantity) {
			//商品的备货数量因为“最大备货量”规则修改前的值，对比修改后值，用于记录备货数变更日志
			$cStoryStartQuantity = $this->_backupQuantity;
			$this->_backupQuantity = $maxBackupQuantity;
		}
		return true;
	}

	/**
	 * 计算 (最大备货量|安全库存) 的基数
	 * @param  [type] $baseCode [description]
	 * @return [type]           [description]
	 */
	private function _calculateBase($baseCode) {
		$baseCode = (int) $baseCode;
		//基数是常量1,那么实际基数就是1
		if ($baseCode === 1) {
			$baseNumber = 1;
		} elseif ($baseCode != 0) {
			//如果是整数值则表示是取多少天的销量值
			$getSalesQuantityMethod = "goodsRealSalesQuantity";
			//基数一个负数(-N)，表示是获取N天的备货销量，反之获取N天的实际销量
			if ($baseCode < 0) {
				$getSalesQuantityMethod = 'goodsBackupSalesQuantity';
			}

			$salesDays = abs($baseCode);
			$goodsSalesQuantity = $this->_productSalesModel->$getSalesQuantityMethod($this->_goodsSn, $this->_warehouseCode, $salesDays);
			if ($goodsSalesQuantity === false) {
				return $this->_error($this->_productSalesModel->getErrorMsg());
			}
			return $goodsSalesQuantity;
		} else {
			return $baseCode;
		}
	}

	/**
	 * 获取商品的平均销量
	 * @return [type]                [description]
	 */
	private function _calculateSalesMean() {
		$this->_productSalesMeanStrategy = new Cron_Model_Backup_SalesMeanStrategy($this->_productBackupRule['avg_sales_formula_type']);
		$salesMean = $this->_productSalesMeanStrategy->getMean($this->_goodsSn, $this->_warehouseCode);
		if ($salesMean === false) {
			return $this->_error($this->_productSalesMeanStrategy->getErrorMsg());
		}
		return $salesMean;
	}

	/**
	 * 获取备货天数
	 * @return [type] [description]
	 */
	private function _getBackupDays() {
		if (is_null($this->_backupDays)) {
			$stockDays = $this->_calculateBackupDays();
			if ($stockDays === false) {
				return false;
			}
			$this->_backupDays = $stockDays;
			$this->_storyBackupLogModel->log("backup_days", $this->_backupDays);
		}
		return $this->_backupDays;
	}

	/**
	 * 获取备货规则设置的备货天数
	 * @return [type] [description]
	 */
	private function _calculateBackupDays() {
		$stockDayRuleType = $this->_productBackupRule['backup_days_delivery_days_type'];
		$productDeliveryDays = $this->_calculateDeliveryDays($stockDayRuleType);
		if ($productDeliveryDays === false) {
			return $this->_error("备货天数的交期天数发生错误,错误原因:" . $this->getErrorMsg());
		}
		return $productDeliveryDays + $this->_productBackupRule['backup_days_shelves_days'] + $this->_productBackupRule['backup_days_quality_testing_days'] + $this->_productBackupRule['backup_days_need_backup_days'];

	}

	/**
	 * 计算商品的交期天数
	 * @param  int    $useDeliveryDaysType [description]
	 * @return [type]                      [description]
	 */
	private function _calculateDeliveryDays(int $useDeliveryDaysType) {
		if (!in_array($useDeliveryDaysType, [1, 2, 3])) {
			return $this->_error('选择的交期类型：' . $useDeliveryDaysType . '错误！', 'useDeliveryDaysTypeError');
		}
		//获取商品交期
		$productPurchaseInfo = $this->_getProductPurchaseInfo();
		if (!$productPurchaseInfo) {
			return $this->_error('未找到产品：' . $this->_goodsSn . '的交期信息！', 'stockDayRuleTypeError');
		}
		$productDeliveryDays = $useDeliveryDaysType == 1 ? $productPurchaseInfo['delivery_min_day'] : (($useDeliveryDaysType == 2) ? $productPurchaseInfo['delivery_max_day'] : 0);
		return $productDeliveryDays;
	}

	/**
	 * 获取产品的采购信息
	 * @return [type]          [description]
	 */
	private function _getProductPurchaseInfo() {
		if (is_null($this->_productPurchaseInfo)) {
			$this->_productPurchaseInfo = $this->_productPurchaseInfoModel->getOne($this->_goodsSn);
		}
		//如果没有产品的交期信息，那么产品的交货周期默认为0
		if (!$this->_productPurchaseInfo) {
			$this->_productPurchaseInfo = [
				'delivery_min_day' => 0,
				'delivery_max_day' => 0,
			];
		}

		return $this->_productPurchaseInfo;
	}

	/**
	 * 获取产品[a|b|c]需求已有数量
	 * @param  [type] $storyType 需求类型[a|b|c]
	 * @return [type]            [description]
	 */
	private function _getProductStoryHasQuantity($storyType) {
		if (!Application_Model_Story::isValidType($storyType)) {
			return $this->_error("获取商品已有需求出错,未知的需求类别:" . $storyType);
		}

		if (is_null($this->_productStory)) {
			//获取产品今日已有的待审核需求
			$this->_productStory = $this->_storyModel->getOneByExamineStatus($this->_goodsSn, $this->_warehouseCode, Application_Model_Story::INIT_EXAMINE_STATUS);
		}
		$storyTypeQuantityFields = ['a' => 'a_story_quantity', 'b' => 'b_story_quantity', 'c' => 'c_story_quantity'];
		$quantityFields = $storyTypeQuantityFields[$storyType] ?? false;
		if (!$quantityFields) {
			return 0;
		}
		return $this->_productStory[$quantityFields] ?? 0;
	}

	/**
	 * 需求是否要被拦截检查
	 * @return bool true为不拦截，false拦截
	 */
	private function _storyInterceptCheck() {
		$productSaleWarehouse = $this->db->fetchOne('select wh_code from p_product where goods_sn=' . $this->db->quote($this->_goodsSn));
		if (!$productSaleWarehouse) {
			throw new Exception('需求c拦截检查出错，未找到产品销售仓库！');
		}

		return $this->_storyModel->interceptCheck($productSaleWarehouse, $this->_warehouseCode);
	}
}